package org.lex.io;

import java.io.Closeable;
import java.io.File;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.concurrent.Callable;

import org.lex.utils.Result;
import org.lex.utils.SystemProperties;


public class IO {
	public static final int EOF = -1;
	public static final String newline = SystemProperties.lineSeparator();
	public static final int DEFAULT_BUF_SIZE = 128;

	private IO() {
	}

	// ============== convenient method on stream operations ===============//
	public static Result closeQuietly(Closeable stream) {
		if (null == stream)
			return Result.error("Null Pointer");
		try {
			stream.close();
			return Result.success();
		} catch (Exception e) {
			return Result.error(e.getMessage());
		}
	}

	public static Result flushQuietly(Flushable stream) {
		if (null == stream)
			return Result.error("Illegal argument");
		try {
			stream.flush();
			return Result.success();
		} catch (Exception e) {
			return Result.error(e.getMessage());
		}
	}

	public static <T> Result callQuietly(Callable<T> call) {
		if (null == call)
			return Result.error("Null Pointer");
		try {
			call.call();
			return Result.success();
		} catch (Exception e) {
			return Result.error(e.getMessage());
		}
	}

	/***************************************************************************
	 * NullInputStream & NullOutputStream
	 **************************************************************************/
	public static InputStream getNullInputStream() {
		return NullInputStream.getInstance();
	}

	public static OutputStream getNullOutputStream() {
		return NullOutputStream.getInstance();
	}

	/***************************************************************************
	 * Utilities for converting an InputStream/Reader to Appendable & String
	 **************************************************************************/
	public static Appendable read(Reader in) throws IOException {
		return read(in, new StringBuilder(), DEFAULT_BUF_SIZE);
	}

	public static Appendable read(Reader in, int bufsize) throws IOException {
		return read(in, new StringBuilder(), bufsize);
	}

	public static Appendable read(Reader in, Appendable to, int bufsize) throws IOException {
		if (null == to)
			to = new StringBuilder();
		if (null == in)
			return to;
		if (bufsize < 1)
			bufsize = 1;
		char[] buffer = new char[bufsize];
		do {
			int len = in.read(buffer);
			if (-1 == len)
				return to;
			to.append(new CharArrayWrapper(buffer, 0, len));
		} while (true);
	}

	public static String toString(InputStream in) throws IOException {
		return toString(new InputStreamReader(in), DEFAULT_BUF_SIZE);
	}

	public static String toString(InputStream in, Charset cs) throws IOException {
		return toString(new InputStreamReader(in, cs), DEFAULT_BUF_SIZE);
	}

	public static String toString(Reader in) throws IOException {
		return toString(in, DEFAULT_BUF_SIZE);
	}

	public static String toString(Reader in, int bufsize) throws IOException {
		return read(in, bufsize).toString();
	}

	/***************************************************************************
	 * file operations
	 **************************************************************************/
	public static String getCanonicalPath(File file) {
		try {
			return file.getCanonicalPath();
		} catch (Exception e) {
			return file.getAbsolutePath();
		}
	}

	public static File getCanonicalFile(File file) {
		try {
			return file.getCanonicalFile();
		} catch (Exception e) {
			return file.getAbsoluteFile();
		}
	}

	static class CharArrayWrapper implements CharSequence {
		static char[] EMPTY = new char[0];
		private char[] data = EMPTY;
		private int offset = 0;
		private int length = 0;

		CharArrayWrapper(char[] value, int offset, int length) {
			this.data = value;
			this.offset = offset;
			this.length = length;
		}

		public char charAt(int index) {
			return data[offset + index];
		}

		public int length() {
			return length;
		}

		public CharSequence subSequence(int start, int end) {
			return new CharArrayWrapper(data, start + offset, end - start);
		}
	}
}
